﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Windows.Forms;
using SdmxMl.Common;
using SdmxMl.Helper;
using SdmxMl.Manager;

namespace SdmxMl.Structure
{
    /// <summary>
    /// KeyFamilyType defines the structure of a key-family description. This includes the name and 
    /// a set of components (attributes and dimensions) as element content, and an ID, agency, version, 
    /// and the URL where located as attributes. The urn attribute holds a valid SDMX Registry URN, as 
    /// per the SDMX Registry spec. The isFinal attribute, once set to true, indicates that no changes 
    /// may be made without versioning. The validFrom and validTo attributes provide inclusive dates for 
    /// providing supplemental validity information about the version. If the isExternalReference attribute 
    /// is true, then the uri attribute must be provided, giving a location where a valid structure message 
    /// can be found containing the full details of the key family.
    /// </summary>
    public class KeyFamilyType : Artefact
    {
        #region Properties

        /// <summary> Time dimension if exist or null </summary>
        public TimeDimensionType TimeDimension
        {
            get
            {
                 // Dimensions refers ConceprScheme and codelists
                foreach (BaseDimensionType bdim in this.DimensionList)
                {
                    DimensionType aDim = bdim as DimensionType;
                    if (aDim == null && bdim is TimeDimensionType)
                    {
                        return bdim as TimeDimensionType;
                    }
                }
                return null;
           }
        }

        public override ArtefactRefTypedList ArtefactRefList
        {
            get
            {
                ArtefactRefTypedList list = new ArtefactRefTypedList();

                // Dimensions refers ConceprScheme and codelists
                foreach (BaseDimensionType bdim in this.DimensionList)
                {
                    DimensionType aDim = bdim as DimensionType;
                    if (aDim == null && bdim is TimeDimensionType)
                    {
                        TimeDimensionType tdim = bdim as TimeDimensionType;
                        // ConceptScheme Ref
                        ArtefactRefTyped at = new ArtefactRefTyped(
                            SdmxMl.Manager.SdmxArtefactType.Concepts,
                            tdim.ConceptSchemeInfo.Id, tdim.ConceptSchemeInfo.AgencyId, tdim.ConceptSchemeInfo.Version);
                        if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                            list.Add(at);

                        // Represaentation only defined for Codelist
                        at = new ArtefactRefTyped(
                            SdmxMl.Manager.SdmxArtefactType.CodeLists,
                            tdim.CodelistInfo.Id, tdim.CodelistInfo.AgencyId, tdim.CodelistInfo.Version);
                        if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                            list.Add(at);

                    }
                    else if (aDim != null)
                    {
                        // ConceptScheme Ref
                        ArtefactRefTyped at = new ArtefactRefTyped(
                            SdmxMl.Manager.SdmxArtefactType.Concepts,
                            aDim.ConceptSchemeInfo.Id, aDim.ConceptSchemeInfo.AgencyId, aDim.ConceptSchemeInfo.Version);
                        if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                            list.Add(at);

                        // Represaentation only defined for Codelist
                        at = new ArtefactRefTyped(
                            SdmxMl.Manager.SdmxArtefactType.CodeLists,
                            aDim.CodelistInfo.Id, aDim.CodelistInfo.AgencyId, aDim.CodelistInfo.Version);
                        if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                            list.Add(at);
                    }
                       
                }

                // Attributes refers ConceptScheme and codelists
                foreach (AttributeType attrib in this.AttributeList)
                {
                    // ConceptScheme Ref
                    ArtefactRefTyped at = new ArtefactRefTyped(
                        SdmxMl.Manager.SdmxArtefactType.Concepts,
                        attrib.ConceptSchemeInfo.Id, attrib.ConceptSchemeInfo.AgencyId, attrib.ConceptSchemeInfo.Version);
                    if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                        list.Add(at);

                    // Represaentation only defined for Codelist
                    at = new ArtefactRefTyped(
                        SdmxMl.Manager.SdmxArtefactType.CodeLists,
                        attrib.CodelistInfo.Id, attrib.CodelistInfo.AgencyId, attrib.CodelistInfo.Version);
                    if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                        list.Add(at);
                }

                // Attributes refers ConceptScheme and codelists
                foreach (BaseDimensionType crt in this.CrossSectionalMeasureList)
                {
                    // ConceptScheme Ref
                    ArtefactRefTyped at = new ArtefactRefTyped(
                        SdmxMl.Manager.SdmxArtefactType.Concepts,
                        crt.ConceptSchemeInfo.Id, crt.ConceptSchemeInfo.AgencyId, crt.ConceptSchemeInfo.Version);
                    if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                        list.Add(at);

                    // Represaentation only defined for Codelist
                    at = new ArtefactRefTyped(
                        SdmxMl.Manager.SdmxArtefactType.CodeLists,
                        crt.CodelistInfo.Id, crt.CodelistInfo.AgencyId, crt.CodelistInfo.Version);
                    if (at.IsFilled && list.Contains(at.Type, at.FullIdent) == false)
                        list.Add(at);
                }

                return list;
            }
        }






        private DimensionTypeList _dimensionList;
        public DimensionTypeList DimensionList
        {
            get { return _dimensionList; }
        }
        private AttributeTypeList _attributeList;
        public AttributeTypeList AttributeList
        {
            get { return _attributeList; }
        }

        private CrossSectionalMeasureTypeList _crossSectionalMeasureList;
        public CrossSectionalMeasureTypeList CrossSectionalMeasureList
        {
            get { return _crossSectionalMeasureList; }
        }

        public PrimaryMeasureType PrimaryMeasure
        {
            // Always one
            get { return _crossSectionalMeasureList.PrimaryMeasure; }
        }

        private GroupTypeList _groupList;
        public GroupTypeList GroupList
        {
            get { return _groupList; }
        }


        #endregion //Properties

        #region Constructors

        public KeyFamilyType()
            : base()
        {
            Init();
            _crossSectionalMeasureList.PrimaryMeasure = new PrimaryMeasureType();
            _crossSectionalMeasureList.Add(_crossSectionalMeasureList.PrimaryMeasure);
        }

        public KeyFamilyType(PrimaryMeasureType p)
            : base()
        {
            Init();
            _crossSectionalMeasureList.PrimaryMeasure = p;
            _crossSectionalMeasureList.Add(_crossSectionalMeasureList.PrimaryMeasure);
        }

        private void Init()
        {
            _dimensionList = new DimensionTypeList(this);
            _crossSectionalMeasureList = new CrossSectionalMeasureTypeList(this);
            _groupList = new GroupTypeList(this);
            _attributeList = new AttributeTypeList(this);
        }

        public KeyFamilyType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr) 
            : base(nodeArtefact, nsmgr)
        {
            Init();
            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {

                // Something inside
                if (nodeArtefact.SelectSingleNode("./structure:Components", nsmgr) != null)
                {
                    string query = "./structure:Components/structure:Dimension";
                    XmlNodeList nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                    if (nodeList.Count > 0)
                        foreach (XmlNode nd in nodeList)
                        {
                            DimensionType dim = new DimensionType(nd, nsmgr);
                            FillMissingCodelistAgencyRef(dim.CodelistInfo);
                            FillMissingConceptSchemeRef(dim, nodeArtefact, nsmgr);
                            _dimensionList.Add(dim);

                            //Special new SMS, remove artificially created codelist reference on measure
                            if (dim.DimKind == DimensionType.DimensionKind.Measure &&
                                dim.CodelistInfo.IsFilled &&
                                dim.CodelistInfo.Id =="CL_" + Id + "_MEASURES")
                            {
                                dim.CodelistInfo.UpdateRef(new ArtefactRefBase()); 
                            }
                        }

                    query = "./structure:Components/structure:TimeDimension";
                    XmlNode node = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (node != null)
                    {
                        TimeDimensionType td = new TimeDimensionType(node, nsmgr);
                        FillMissingCodelistAgencyRef(td.CodelistInfo);
                        FillMissingConceptSchemeRef(td, nodeArtefact, nsmgr);

                        // TimeDim first in list
                        _dimensionList.Insert(0, td);
                    }

                    query = "./structure:Components/structure:CrossSectionalMeasure";
                    nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                    if (nodeList.Count > 0)
                        foreach (XmlNode nd in nodeList)
                        {
                            CrossSectionalMeasureType csm = new CrossSectionalMeasureType(nd, nsmgr);
                            _crossSectionalMeasureList.Add(csm);
                        }

                    query = "./structure:Components/structure:Group";
                    nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                    if (nodeList.Count > 0)
                        foreach (XmlNode nd in nodeList)
                        {
                            GroupType gr = new GroupType(nd, nsmgr);
                            _groupList.Add(gr);
                        }

                    query = "./structure:Components/structure:Attribute";
                    nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                    if (nodeList.Count > 0)
                        foreach (XmlNode nd in nodeList)
                        {
                            AttributeType at = new AttributeType(nd, nsmgr);
                            FillMissingCodelistAgencyRef(at.CodelistInfo);
                            FillMissingConceptSchemeRef(at, nodeArtefact, nsmgr);
                            _attributeList.Add(at);
                        }

                    query = "./structure:Components/structure:PrimaryMeasure";
                    node = nodeArtefact.SelectSingleNode(query, nsmgr);
                    if (node != null)
                    {
                        PrimaryMeasureType pri = new PrimaryMeasureType(node, nsmgr);
                        FillMissingConceptSchemeRef(pri, nodeArtefact, nsmgr);
                        _crossSectionalMeasureList.PrimaryMeasure = pri;
                        _crossSectionalMeasureList.Insert(0, pri);
                    }
                    else
                        throw new ApplicationException("Missing Primary Measure!");
                }
            }
            else
                ConstructFromSdmx_21(nodeArtefact, nsmgr);

        }
        /// <summary>
        /// SDMX 2.1 constructor
        /// </summary>
        /// <param name="nodeArtefact"></param><param name="nsmgr"></param>
        private void ConstructFromSdmx_21(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr)
        {
            // If something inside
            if (nodeArtefact.SelectSingleNode("./structure:DataStructureComponents", nsmgr) != null)
            {
                string query = "./structure:DataStructureComponents/structure:DimensionList/structure:Dimension";
                XmlNodeList nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                {
                    foreach (XmlNode nd in nodeList)
                    {
                        DimensionType dim = new DimensionType(nd, nsmgr);
                        _dimensionList.Add(dim);
                    }
                }

                // Measures
                query = "./structure:DataStructureComponents/structure:DimensionList/structure:MeasureDimension";
                nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                {
                    foreach (XmlNode nd in nodeList)
                    {
                        DimensionType dim = new DimensionType(nd, nsmgr);
                        dim.DimKind = DimensionType.DimensionKind.Measure;
                        _dimensionList.Add(dim);
                    }
                }

                query = "./structure:DataStructureComponents/structure:DimensionList/structure:TimeDimension";
                XmlNode node = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (node != null)
                {
                    TimeDimensionType td = new TimeDimensionType(node, nsmgr);
                    // TimeDim first in list
                    _dimensionList.Insert(0, td);
                }

                // Group(s)
                query = "./structure:DataStructureComponents/structure:Group";
                nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                    foreach (XmlNode nd in nodeList)
                    {
                        GroupType gr = new GroupType(nd, nsmgr);
                        _groupList.Add(gr);
                    }

                query = "./structure:DataStructureComponents/structure:AttributeList/structure:Attribute";
                nodeList = nodeArtefact.SelectNodes(query, nsmgr);
                if (nodeList.Count > 0)
                    foreach (XmlNode nd in nodeList)
                    {
                        AttributeType at = new AttributeType(nd, nsmgr);
                        _attributeList.Add(at);
                    }

                query = "./structure:DataStructureComponents/structure:MeasureList/structure:PrimaryMeasure";
                node = nodeArtefact.SelectSingleNode(query, nsmgr);
                if (node != null)
                {
                    PrimaryMeasureType pri = new PrimaryMeasureType(node, nsmgr);
                    _crossSectionalMeasureList.PrimaryMeasure = pri;
                    _crossSectionalMeasureList.Insert(0, pri);
                }
                else
                    throw new ApplicationException("Missing Primary Measure!");
            }
       }


        // Ensure reference for navigation (it is a guess)
        private void FillMissingCodelistAgencyRef(CodelistReference clr)
        {
            if (string.IsNullOrEmpty(clr.Id) == false &&
                    string.IsNullOrEmpty(clr.AgencyId))
                clr.AgencyId = this.AgencyId;
        }

        private void FillMissingConceptSchemeRef(BaseDimensionType bd, XmlNode nodeArtefact, XmlNamespaceManager nsmgr)
        {
            if (string.IsNullOrEmpty(bd.ConceptInfo.Id) == false &&
                string.IsNullOrEmpty(bd.ConceptSchemeInfo.Id))
            {
                if (nodeArtefact.OwnerDocument != null)
                {
                    string query = string.Format("//structure:Concept[@id='{1}']", SdmxArtefactType.Concepts, bd.ConceptInfo.Id);
 //                   string query = string.Format("//S:{0}/structure:Concept[@id='{1}']", SdmxArtefactType.Concepts, bd.ConceptInfo.Id);
                    XmlNodeList ndlConcept = nodeArtefact.OwnerDocument.SelectNodes(query, nsmgr);
                    if (ndlConcept.Count == 1)
                    {
                        XmlNode parentSchemeNode = ndlConcept[0].ParentNode;
                        if (parentSchemeNode.Name == "ConceptScheme")
                        {
                            string csAgency = Xmlhelper.GetAtt(parentSchemeNode, "agencyID");
                            string  csId = Xmlhelper.GetAtt(parentSchemeNode, "id");
                            if (csAgency.Length > 0)
                                bd.ConceptSchemeInfo.AgencyId = csAgency;
                            if (csId.Length > 0)
                                bd.ConceptSchemeInfo.Id = csId;
                        }
                        else
                        {
                            // Could be global concepts of SDMX 1.0
                            string a = Xmlhelper.GetAtt(ndlConcept[0], "agencyID");
                            if (a.Length > 0)
                            {
                                bd.ConceptSchemeInfo.Id = ConceptSchemeType.GlobalIdBase + a;
                                bd.ConceptSchemeInfo.AgencyId = a;
                            }
                        }

                    }
                }

            }

            // Needs to refers a simulated Global Scheme ?
            if (Id.Length == 0)
            {
            }

        }
        #endregion //Constructors

        #region Tree

        public override TreeNode FillNode(TreeNode parentNode)
        {
            TreeNode t = base.FillNode(parentNode);
            TreeNode td = new TreeNode(_dimensionList.ToString(), 0, 0);
            t.Nodes.Add(td);
            td.Tag = _dimensionList;

            td = new TreeNode(_crossSectionalMeasureList.ToString(), 0, 0);
            t.Nodes.Add(td);
            td.Tag = _crossSectionalMeasureList;

            td = new TreeNode(_attributeList.ToString(), 0, 0);
            t.Nodes.Add(td);
            td.Tag = _attributeList;

            td = new TreeNode(_groupList.ToString(), 1, 1);
            t.Nodes.Add(td);
            td.Tag = _groupList;
            foreach (GroupType gr in _groupList)
                gr.FillNode(td);


            return t;
        }

        #endregion // Tree

        #region XML serialize

        public override void WriteXmlInsider(XmlTextWriter xw)
        {
            string prefix = "structure";

           if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_20)
            {
                if (_dimensionList.Count > 0)
                {
                    xw.WriteStartElement(prefix, "Components", null);

                    foreach (BaseDimensionType bdim in _dimensionList)
                    {
                        // Skip TimeDimension
                        DimensionType dim = bdim as DimensionType;
                        if (dim != null)
                            dim.WriteXml(xw, prefix, "Dimension");
                    }

                    TimeDimensionType tt = _dimensionList.GetTimeDimension();
                    if (tt != null)
                        tt.WriteXml(xw, prefix, "TimeDimension");

                    foreach (GroupType tdim in _groupList)
                        tdim.WriteXml(xw, prefix, "Group");

                    _crossSectionalMeasureList.PrimaryMeasure.WriteXml(xw, prefix, "PrimaryMeasure");

                    // 1st is primary measure, not cross measure
                    _crossSectionalMeasureList.Remove(_crossSectionalMeasureList.PrimaryMeasure);
                    foreach (CrossSectionalMeasureType csm in _crossSectionalMeasureList)
                        csm.WriteXml(xw, prefix, "CrossSectionalMeasure");
                    _crossSectionalMeasureList.Insert(0, _crossSectionalMeasureList.PrimaryMeasure);

                    foreach (AttributeType at in _attributeList)
                        at.WriteXml(xw, prefix, "Attribute");

                    xw.WriteEndElement();
                }
            }
            else
            {
                WriteXml_21_Insider(xw, prefix);
            }
        }

        private void WriteXml_21_Insider(XmlTextWriter xw, string prefix)
        {
            if (_dimensionList.Count > 0)
            {
                xw.WriteStartElement(prefix, "DataStructureComponents", null);

                #region Dimensions

                // Dim List
                xw.WriteStartElement(prefix, "DimensionList", null);
                foreach (BaseDimensionType bdim in _dimensionList)
                {
                    // Skip TimeDimension & measure dim
                    DimensionType dim = bdim as DimensionType;
                    if (dim != null && dim.DimKind != DimensionType.DimensionKind.Measure)
                        dim.WriteXml(xw, prefix, "Dimension");
                }
                /*
                foreach (BaseDimensionType bdim in _dimensionList)
                {
                    // Skip TimeDimension & non measure dim
                    DimensionType dim = bdim as DimensionType;
                    if (dim != null && dim.DimKind == DimensionType.DimensionKind.Measure)
                        dim.WriteXml(xw, prefix, "MeasureDimension");
                }
                */
                if (_crossSectionalMeasureList.Count > 1)
                {
                    foreach (BaseDimensionType bdim in _crossSectionalMeasureList)
                    {
                        CrossSectionalMeasureType csm = bdim as CrossSectionalMeasureType;
                        if (csm != null)
                            csm.WriteXml(xw, prefix, "MeasureDimension");
                    }
                }

                TimeDimensionType tt = _dimensionList.GetTimeDimension();
                if (tt != null)
                {
                    tt.Id = "TIME_PERIOD"; // Fixed 2.1

                    tt.WriteXml(xw, prefix, "TimeDimension");
                }

                xw.WriteEndElement(); // End DimensionList
                #endregion

                #region Groups

                foreach (GroupType grp in _groupList)
                    grp.WriteXml(xw, prefix, "Group");
                #endregion

                #region Attributes

                if (_attributeList.Count > 0)
                {
                    xw.WriteStartElement(prefix, "AttributeList", null);
                    foreach (AttributeType at in _attributeList)
                        at.WriteXml(xw, prefix, "Attribute");
                    xw.WriteEndElement();
                }

                #endregion

                #region PrimaryMeasure
                xw.WriteStartElement(prefix, "MeasureList", null);
                _crossSectionalMeasureList.PrimaryMeasure.WriteXml(xw, prefix, "PrimaryMeasure");
                xw.WriteEndElement();

                #endregion

                xw.WriteEndElement(); // End DataStructureComponents

            }
       }

        #endregion

        #region Measure helper

        public List<DimensionType> MeasureDimensionList
        {
            get { return _dimensionList.GetMeasureDimensions(); }
        }

        public bool HasMeasureDimension
        {
            get { return MeasureDimensionList.Count > 0; }
        }

        #endregion //Measure helper

        #region Artefact Type

        public override Manager.SdmxArtefactType ArType
        {
            get { return Manager.SdmxArtefactType.KeyFamilies; }
        }

        #endregion

        public BaseDimensionType LocateItem(string conceptId)
        {
            BaseDimensionType bd = this.AttributeList.GetDimensionFromName(conceptId);
            if (bd == null)
                bd = this.DimensionList.GetDimensionFromName(conceptId);
            if (bd == null)
                bd = this.CrossSectionalMeasureList.GetDimensionFromName(conceptId);

            return bd;
        }

    }
}
